<script setup lang="ts">
import { computed, ref, onMounted, onUnmounted, watch, type FunctionDirective } from 'vue';
import { Content, inBrowser, useData, useRoute, useRouter } from 'vitepress';
import { ODivider, OIcon, OLink, vLoading } from '@opensig/opendesign';
import DocBreadCrumb from '@/components/doc/DocBreadCrumb.vue';
import DocMenu from '@/components/doc/DocMenu.vue';
import DocSearch from '@/components/doc/DocSearch.vue';
import { useScreen } from '@/composables/useScreen';
import DocAnchor from '@/components/doc/DocAnchor.vue';
import DocFooter from '@/components/doc/DocFooter.vue';
import { storeToRefs } from 'pinia';
import { useCommonStore, useSearchingStore } from '@/stores/common.ts';
import SearchResult from '@/components/search/SearchResult.vue';
import { useMenuStore } from '@/stores/menu.ts';
import useSelect from '@/composables/useSelect.ts';
import DocFeedback from '@/components/doc/DocFeedback.vue';
import LayoutFooter from '@/layouts/LayoutNewFooter.vue';
import FloatingButton from '@/components/FloatingButton.vue';
import SubHeader from '@/components/header/SubHeader.vue';
import DocTools from '@/components/doc/DocTools.vue';
import { scrollToTop } from '@/utils/common.ts';
import IconOutlink2 from '~icons/app/icon-outlink2.svg';
import { GITCODE_URL } from '@/config/url-config';
import { oaReport } from '@/shared/analytics';
const commonStore = useCommonStore();

const { lePad, isPhone } = useScreen();
const { frontmatter, page } = useData();

// -------------------- 菜单配置 --------------------
const menuStore = useMenuStore();
const { lastBread } = storeToRefs(menuStore);
const computedTitle = computed(() => frontmatter.value.title || lastBread?.value?.label || '');
// -------------------- 屏幕小于1200px后，切换是否折叠--------------------
const isSidebarHidden = ref(true);
const switchMenu = () => {
  isSidebarHidden.value = !isSidebarHidden.value;
};

// -------------------- 搜索 --------------------
const searchingStore = useSearchingStore();
const { isSearching, isLoading } = storeToRefs(searchingStore);

// -------------------- 选择文本 --------------------
const { visible, x, y } = useSelect('.markdown-content');
// -------------------- 页面切换或hash切换 --------------------
const hashChanged = () => {
  const hash = window.location.hash;
  if (hash && inBrowser) {
    const ele = document.querySelector(decodeURIComponent(hash));
    if (ele) {
      ele.scrollIntoView({ behavior: 'smooth' });
    } else {
      scrollToTop(0, false);
    }
  } else {
    scrollToTop(0, false);
  }
};

const route = useRoute();
watch(
  () => route.path,
  () => {
    hashChanged();
  }
);

const loaded = ref(false);

onMounted(() => {
  loaded.value = true;
  window.addEventListener('hashchange', hashChanged);
});

onUnmounted(() => {
  window.removeEventListener('hashchange', hashChanged);
});

const { showMobileNav } = storeToRefs(commonStore);

// ----------------埋点----------------

// 上报跳转链接点击事件
const reportLink = (e: MouseEvent) => {
  let el = e.target as HTMLElement;
  if (el === e.currentTarget) return;
  while (el.tagName !== 'A') {
    el = el.parentElement!;
    if (!el || el === e.currentTarget) {
      return;
    }
  }
  return {
    service: 'docs',
    properties: {
      $url: location.href,
      target: el.textContent,
      detail: (el as HTMLAnchorElement).href,
      type: 'link',
    },
  };
};

// ----------------各章节阅读时长埋点----------------
let obs: IntersectionObserver | null = null;
type Section = { start: HTMLElement; end: HTMLElement | null; startTime: number | null };
let sections: Section[];
const currentReadingSections = new Set<Section>();

// 文档内容更新时触发
const vContentUpdated: FunctionDirective<HTMLElement> = (el) => {
  updateObserver(el);
  reportReadEvent();
};

// 上报阅读时长
const reportReadEvent = () => {
  // 如果当前阅读的章节中有内容,就上报阅读时长
  for (const section of currentReadingSections) {
    if (!section.startTime) continue;
    const duration = Date.now() - section.startTime;
    oaReport(
      'sectionDuration',
      {
        section: section.start.id,
        duration: duration,
        $url: location.href,
      },
      'docs'
    );
    section.startTime = null;
  }
  currentReadingSections.clear();
};

// 上报滚动高度
const reportScrollHeight = () => {
  const container = document.querySelector<HTMLElement>('.o-scroller-container');
  if (!container) return;
  oaReport('scroll', {
    scrollTop: container.scrollTop,
    $url: location.href,
    perentage: ((container.scrollTop * 100) / (container.scrollHeight - container.clientHeight)).toFixed(2) + '%',
  }, 'docs');
};

const router = useRouter();
let visibilityChangeCb: () => void;

onMounted(() => {
  router.onBeforeRouteChange = () => {
    reportScrollHeight();
  };
  // [离开/关闭]页面的时候上报[文档滚动高度/当前正在阅读的章节停留时长]
  document.addEventListener(
    'visibilitychange',
    (visibilityChangeCb = () => {
      if (document.visibilityState === 'hidden') {
        reportReadEvent();
        reportScrollHeight();
      }
    })
  );
});

const updateObserver = (contentEl: HTMLElement) => {
  if (!obs) {
    setupObs();
  }
  obs!.disconnect();
  // 搜集文档中所有标题元素，算作章节
  const titles = contentEl.firstElementChild?.querySelectorAll('h1');
  if (!titles?.length) return;
  sections = [];
  for (const el of titles) {
    if (sections.length > 0) {
      sections[sections.length - 1].end = el.previousElementSibling as HTMLElement;
    }
    sections.push({ start: el as HTMLElement, end: null, startTime: null });
  }
  sections.forEach((section) => {
    obs?.observe(section.start);
    if (section.end) {
      obs?.observe(section.end);
    }
  });
};

const setupObs = () => {
  obs = new IntersectionObserver(
    (entries) => {
      for (const ent of entries) {
        // 如果某个章节标题出现在视口，则算开始阅读，记录开始阅读时刻
        if (ent.intersectionRatio >= 1 && ent.isIntersecting && ent.target.tagName === 'H1') {
          const section = sections.find((section) => section.start === ent.target);
          if (!section) continue;
          currentReadingSections.add(section);
          section.startTime = Date.now();
          continue;
        }
        // 标题元素的前一个元素作为他的上一个章节的结尾，从页面顶部消失时算上一个章节阅读结束
        if (!ent.isIntersecting && ent.intersectionRatio <= 0 && ent.target.tagName !== 'H1' && ent.boundingClientRect.top < ent.rootBounds!.top) {
          const section = sections.find((section) => section.end === ent.target);
          if (!section || !section.startTime) continue;
          const duration = Date.now() - section.startTime;
          section.startTime = null;
          currentReadingSections.delete(section);
          oaReport(
            'sectionDuration',
            {
              section: section.start.id,
              duration: duration,
              $url: location.href,
            },
            'docs'
          );
          continue;
        }
        // 当某个标题元素从页面底部消失，将这一章从“当前正在阅读的章节”中移除
        if (!ent.isIntersecting && ent.intersectionRatio <= 0 && ent.target.tagName === 'H1' && ent.boundingClientRect.top >= window.innerHeight) {
          const section = sections.find((section) => section.start === ent.target);
          if (!section) continue;
          currentReadingSections.delete(section);
        }
      }
    },
    { threshold: [0, 1.0], rootMargin: '-72px 0px 0px 0px' }
  );
};

onUnmounted(() => {
  obs?.disconnect();
  sections = [];
  currentReadingSections.clear();
  router.onBeforeRouteChange = undefined;
  document.removeEventListener('visibilitychange', visibilityChangeCb);
});
</script>

<template>
  <div class="layout-doc-header" v-if="isPhone">
    <SubHeader :title="computedTitle" @toggle-menu="switchMenu" :open="!isSidebarHidden"></SubHeader>
  </div>
  <div class="layout-doc">
    <ClientOnly>
      <div class="layout-doc-aside" :class="[lePad && isSidebarHidden ? 'is-closed' : '']" v-if="!showMobileNav">
        <DocSearch />
        <DocMenu
          v-analytics.catchBubble.addUrl="{
            service: 'docs',
            properties: {
              type: 'menu',
            },
          }"
          @switchVisible="isSidebarHidden = true"
        />
        <div class="menu-opener" @click="switchMenu">
          <div class="opener-thumb"></div>
        </div>
      </div>
      <div class="aside-mask" @click="switchMenu" v-if="!showMobileNav"></div>
    </ClientOnly>
    <div :class="['layout-doc-content', isSearching && 'is-searching']">
      <ClientOnly>
        <DocAnchor v-if="!isSearching && !showMobileNav" />
      </ClientOnly>
      <div class="breadcrumb">
        <DocBreadCrumb />
      </div>
      <div class="markdown-content-wrapper" v-loading="isLoading">
        <div class="markdown-content">
          <template v-if="!isSearching">
            <div class="doc-header">
              <div class="doc-header-left">
                <div class="doc-title">{{ computedTitle }}</div>
                <div class="doc-desc">
                  <div class="doc-update-date" v-if="frontmatter.date">更新时间：{{ frontmatter.date }}</div>
                  <ODivider direction="v" v-if="frontmatter.date" />
                  <OLink
                    color="primary"
                    class="doc-gitcode"
                    :href="`${GITCODE_URL}docs/blob/main/docs/${page.filePath}`"
                    target="_blank"
                    rel="noopener noreferrer"
                    v-analytics.addUrl="{ service: 'docs', properties: { target: '在Gitcode上查看源码', type: 'link' } }"
                  >
                    <span>在Gitcode上查看源码</span>
                    <OIcon><IconOutlink2></IconOutlink2></OIcon>
                  </OLink>
                </div>
              </div>
              <div class="doc-header-right">
                <ClientOnly>
                  <DocTools :title="computedTitle"></DocTools>
                </ClientOnly>
              </div>
            </div>
            <Content v-content-updated v-analytics="reportLink" class="markdown-body docs-content"></Content>
          </template>
          <template v-else>
            <SearchResult />
          </template>
        </div>
        <DocFooter v-if="!isSearching" />
      </div>
      <LayoutFooter></LayoutFooter>
    </div>
    <ClientOnly>
      <DocFeedback type="select" v-model:visible="visible">
        <div
          :style="{
            '--x': x + 'px',
            '--y': y + 'px',
          }"
          class="select-feedback-placeholder"
        />
      </DocFeedback>
      <FloatingButton :last-bread="lastBread" />
    </ClientOnly>
  </div>
</template>

<style lang="scss">
.figure-preview {
  width: 0;
  height: 0;
  overflow: hidden;
  position: fixed;
  left: 0;
  top: 0;
}

.o-figure-preview-layer {
  .o-figure-preview-img {
    img {
      background-color: #fff;
      height: 80vh;
      max-height: 80vh;
      max-width: 80vw;
    }
  }
}
</style>

<style scoped lang="scss">
.layout-doc-header {
  display: none;
  @include respond-to('phone') {
    display: block;
  }
}
.layout-doc {
  display: flex;
  padding-top: var(--layout-doc-padding-top);
  --padding-x: 72px;

  .aside-mask {
    background-color: var(--o-color-mask1);
    bottom: 0;
    content: '';
    left: 0;
    opacity: 0;
    pointer-events: none;
    position: fixed;
    right: 0;
    top: 0;
    transition: var(--all-transition);
    z-index: 34;
    @include respond-to('<=pad') {
      opacity: 1;
      pointer-events: auto;
    }
  }

  .layout-doc-aside {
    width: calc(var(--layout-doc-menu-width) + var(--grid-column-gutter));
    position: fixed;
    top: calc(var(--layout-header-height) + var(--layout-doc-padding-top));
    z-index: 35;
    bottom: 0;

    &.is-closed {
      transform: translate(-100%);

      .menu-opener {
        border-radius: 0 var(--o-radius-s) var(--o-radius-s) 0;
        box-shadow: var(--o-shadow-2);
      }

      & + .aside-mask {
        opacity: 0;
        pointer-events: none;
      }
    }

    @include respond-to('<=pad') {
      top: var(--layout-header-height);
      background-color: var(--o-color-fill2);
      padding-left: 24px;
      padding-right: 24px;
      padding-top: 24px;
      left: 0;
    }
    @include respond-to('phone') {
      top: calc(var(--layout-header-height) + var(--layout-doc-header-height));
      padding-top: 8px;
      .doc-search {
        margin-bottom: 8px;
      }
    }

    &.is-closed + .aside-mask {
      opacity: 0;
    }

    &:not(.is-closed) .menu-opener {
      --padding-h: 28px;
      --padding-l: 6px;
      --padding-r: 6px;
      background-color: #fff0;
      background-image: url(@/assets/category/doc/menu-switch-bar.png);
      background-position: 50%;
      background-repeat: no-repeat;
      background-size: contain;

      .opener-thumb {
        background-color: var(--o-color-ubmc);
      }
    }

    .menu-opener {
      --thumb-height: 32px;
      --thumb-width: 3px;
      --padding-h: 16px;
      --padding-l: 6px;
      --padding-r: 6px;
      --height: calc(var(--padding-h) * 2 + var(--thumb-height));
      background-color: rgba(var(--o-mixedgray-1), 1);
      cursor: pointer;
      font-size: 24px;
      padding: var(--padding-h);
      padding-left: var(--padding-l);
      padding-right: var(--padding-r);
      position: absolute;
      right: 0;
      top: 50%;
      transform: translate(100%, -50%);
      transition:
        background-image 0.2s linear,
        border-radius 0.5s linear;
      z-index: 5;
      @include respond-to('>pad') {
        display: none;
      }
      @include respond-to('phone') {
        display: none;
      }

      .opener-thumb {
        background-color: var(--o-color-ubmc);
        border-radius: 100px;
        height: var(--thumb-height);
        width: var(--thumb-width);
      }
    }
  }

  .layout-doc-content {
    width: var(--grid-full-width);
    padding-left: calc(var(--layout-doc-menu-width) + var(--grid-column-gutter));
    padding-right: calc(var(--layout-doc-anchor-width) + var(--grid-column-gutter));

    @include respond-to('<=laptop') {
      padding-right: 0;
    }
    @include respond-to('<=pad') {
      padding-left: 0;
    }

    .docs-content {
      padding-top: var(--o-gap-6);
    }

    .markdown-content-wrapper {
      border-radius: 8px;
      background-color: var(--o-color-fill2);
      position: relative;
      min-height: calc(100vh - var(--layout-header-height) - var(--layout-footer-height) - 130px);

      .markdown-content {
        background-image: url('@/assets/images/doc-bg.png');
        padding: 40px var(--padding-x) 32px;
        background-position: top 0 right 50%;
        background-repeat: no-repeat;
        background-size: 100% auto;
        border-radius: 4px;
        @include respond-to('pad_h') {
          padding-top: 16px;
          padding-bottom: 16px;
        }
        @include respond-to('pad_v') {
          padding-top: 12px;
          padding-bottom: 12px;
        }
        @include respond-to('phone') {
          padding: var(--padding-x);
        }
      }

      .download-doc {
        //background-image: none;
        .doc-header-right {
          display: none;
        }

        :deep(code) {
          white-space: break-spaces;
        }
      }

      .doc-header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        color: var(--o-color-info1);

        .doc-title {
          @include h2;
          font-weight: bold;
        }

        .doc-desc {
          margin-top: var(--o-gap-2);
          display: flex;
          align-items: center;
          gap: 4px;
          @include tip1;

          .doc-update-date {
            color: var(--o-color-info2);
          }
          .doc-gitcode {
            cursor: pointer;
          }
          :deep(.o-link) {
            .o-link-main {
              display: flex;
              align-items: center;
              gap: 4px;
            }
            .o-icon {
              font-size: 16px;
            }
          }
        }

        .doc-header-right {
          @include respond-to('phone') {
            display: none;
          }
        }
      }

      .doc-content {
        margin-top: var(--o-gap-8);
      }
    }
  }

  .doc-anchor {
    width: var(--layout-doc-anchor-width);
    height: calc(var(--layout-content-min-height) - var(--layout-doc-anchor-top) - var(--layout-header-height));
    z-index: 5;
    border-radius: var(--o-radius-m);
  }

  .select-feedback-placeholder {
    position: absolute;
    left: var(--x);
    top: var(--y);
  }

  @include respond-to('laptop') {
    --padding-x: 56px;
  }
  @include respond-to('pad_h') {
    --padding-x: 40px;
  }
  @include respond-to('pad_v') {
    --padding-x: 32px;
  }
  @include respond-to('phone') {
    --padding-x: 12px;
  }
}
</style>

<style lang="scss">
@include in-dark {
  .layout-doc {
    .layout-doc-aside:not(.is-closed) .menu-opener {
      filter: invert(100%);

      .opener-thumb {
        filter: invert(100%);
      }
    }

    .layout-doc-content .markdown-content-wrapper .markdown-content {
      background-image: url('@/assets/images/doc-bg_dark.svg');
    }
  }
}
</style>
